package com.cd2cd.util.project.apidoc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.cd2cd.domain.*;
import com.cd2cd.mapper.*;
import com.cd2cd.util.CommUtils;
import com.cd2cd.util.ObjectTypeUtil;
import com.google.common.collect.ImmutableMap;
import io.github.yedaxia.apidocs.parser.ControllerNode;
import io.github.yedaxia.apidocs.parser.ParamNode;
import io.github.yedaxia.apidocs.parser.RequestNode;
import io.github.yedaxia.apidocs.parser.ResponseNode;
import org.apache.commons.collections.CollectionUtils;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *
 */
@Component
public class ControllerNodeUtil {

    private Logger log = LoggerFactory.getLogger(ControllerNodeUtil.class);

    private final String BASE_HOST = "/comm/curd/%s/%s";
    private final String C = "C";
    private final String U = "U";
    private final String R = "R";
    private final String D = "D";
    private final String Q = "Q";
    private final String L = "L";

    private Map<String, String> CURD_TITLE = ImmutableMap.of("C", "添加", "U", "更新", "R", "详情", "D", "删除", "L", "列表");
    private Map<String, String> mysql2java = new HashMap<>();

    @Resource
    private ProTableMapper proTableMapper;

    @Resource
    private ProFileMapper proFileMapper;

    @Resource
    private ProFunArgMapper proFunArgMapper;

    @Resource
    private ProTableColumnMapper proTableColumnMapper;

    @Resource
    private ProFieldMapper proFieldMapper;

    /**
     * 1、需要获取curd controller相关
     * 2、获取 controller 配置样关
     *
     * @param projectId
     * @return
     */
    public List<ControllerNode> getControllerNodeByProjectId(Long projectId) {

        List<ControllerNode> contrNodes = new ArrayList<>();
        List<ControllerNode> curdControllerNodes = getCurdControllerNode(projectId);
        contrNodes.addAll(curdControllerNodes);

        curdControllerNodes = getCustomControllerNode(projectId);
        contrNodes.addAll(curdControllerNodes);

        return contrNodes;
    }

    private String getJavaTypeFromJdbcType(String mysqlType) {
        if (mysql2java.containsKey(mysqlType)) {
            return mysql2java.get(mysqlType);
        }
        // DECIMAL 3
        IntrospectedColumn introspectedColumn = ObjectTypeUtil.getIntrospectedColumn(mysqlType);
        JavaTypeResolver javaTypeResolver = new JavaTypeResolverDefaultImpl();
        FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver.calculateJavaType(introspectedColumn);
        mysql2java.put(mysqlType, fullyQualifiedJavaType.getShortName().toLowerCase());

        return fullyQualifiedJavaType.getShortName().toLowerCase();
    }

    /**
     * getC(), getU(),getR(),getD(),getQ()
     *
     * @param cc
     * @param TableName
     * @param curdField
     * @param flag
     */
    private void setCurdFiledMap(ProTableColumn cc, String TableName, Map<String, List<ProTableColumn>> curdField, String flag) {

        try {
            Method mm = ProTableColumn.class.getMethod("get" + flag);

            String val = (String) mm.invoke(cc);
            List<ProTableColumn> ff = curdField.get(flag);
            if (null == ff) {
                ff = new ArrayList<>();
                curdField.put(flag, ff);
            }

            if ("yes".equalsIgnoreCase(val) || (Q.equalsIgnoreCase(flag) && !"no".equalsIgnoreCase(val))) {
                ff.add(cc);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private List<ControllerNode> getCustomControllerNode(Long projectId) {
        List<ControllerNode> cons = new ArrayList<>();

        List<ProFile> controllers = proFileMapper.queryFileTreeByProjectId(projectId);
        for (int i = 0; i < controllers.size(); i++) {
            ProFile conFile = controllers.get(i);

            ControllerNode contrNode = new ControllerNode();
            cons.add(contrNode);

            contrNode.setDescription(Optional.ofNullable(conFile.getComment()).orElse(conFile.getName()));
            contrNode.setDocFileName(conFile.getName());

            List<RequestNode> requestNodes = new ArrayList<>();
            contrNode.setRequestNodes(requestNodes);

            String bbUrl = conFile.getReqPath();
            List<ProFun> funs = conFile.getFuns();
            for (int k = 0; k < funs.size(); k++) {
                ProFun fun = funs.get(k);

                RequestNode requestNode = new RequestNode();
             //   requestNodes.add(requestNode);

                // base info
                requestNode.setChangeFlag(Byte.valueOf("1"));
                requestNode.setUrl(bbUrl + fun.getReqPath());
                requestNode.setDeprecated(false);
                requestNode.setMethodName(fun.getFunName());
                requestNode.setMethod(Arrays.asList(fun.getReqMethod()));
                requestNode.setDescription(Optional.ofNullable(fun.getComment()).orElse(""));
                requestNode.setCodeFileUrl(contrNode.getDocFileName() + ".html#" + fun.getFunName());


                // params
                List<ParamNode> paramNodes = new ArrayList<>();
                requestNode.setParamNodes(paramNodes);
                List<ProFunArg> args = fun.getArgs();
                boolean voMatch = args.stream().anyMatch(item -> "vo".equalsIgnoreCase(item.getArgType()));
                ParamNode paramNode = null;
                if(voMatch){
                    paramNode = new ParamNode();
                    paramNode.setJsonBody(true);
                    paramNode.setDescription(getPrettyString(getParamsDesc(args).toJSONString()));
                    paramNodes.add(paramNode);
                }else{
                    for (int h = 0; h < args.size(); h++) {
                        ProFunArg arg = args.get(h);
                        paramNode = new ParamNode();

                        paramNode.setName(arg.getName());
                        paramNode.setRequired(true);
                        paramNode.setType(arg.getArgTypeName().toLowerCase());
                        paramNodes.add(paramNode);
                    }
                }

                // response
                ResponseNode responseNode = new ResponseNode();
                // resJSON
                JSONObject resJson = new JSONObject();
                resJson.put("code", "/* 业务状态码 */");
                resJson.put("msg", "/* 业务状态信息 */");
                resJson.put("data",getObjectType(getReturnVos(fun.getReturnVo())));

                responseNode.setStringResult(resJson.toJSONString());
                requestNode.setResponseNode(responseNode);
                contrNode.getRequestNodes().add(requestNode);
            }
        }

        return cons;
    }

    private List<ControllerNode> getCurdControllerNode(Long projectId) {
        List<ControllerNode> curdControllerNodes = new ArrayList<>();

        List<ProTable> tables = proTableMapper.queryCurdTableListByProId(projectId);

        for (int i = 0; i < tables.size(); i++) {

            ProTable tab = tables.get(i);
            ControllerNode contrNode = new ControllerNode();
            /**
             * description
             * docFileName
             * - requestNodes
             *   codeFileUrl
             *   changeFlag 1
             */
            String table_name = tab.getName();
            String TableName = CommUtils.underlineToCamel(table_name, Boolean.TRUE);
            String comment = Optional.ofNullable(tab.getComment()).orElse(TableName);

            // 设置 controller node value
            contrNode.setDescription(String.format("%s查询控制器", comment));
            contrNode.setDocFileName(String.format("%sController", TableName));

            // 缓存参数
            List<ProTableColumn> columns = tab.getColumns();
            Map<String, List<ProTableColumn>> curdField = new HashMap<>();

            for (int j = 0; j < columns.size(); j++) {
                ProTableColumn cc = columns.get(j);

                setCurdFiledMap(cc, TableName, curdField, C);
                setCurdFiledMap(cc, TableName, curdField, U);
                setCurdFiledMap(cc, TableName, curdField, R);
                setCurdFiledMap(cc, TableName, curdField, Q);
                setCurdFiledMap(cc, TableName, curdField, L);

            }

            setCurdRequestNodeByFlag(C, TableName, comment, curdField, contrNode);
            setCurdRequestNodeByFlag(U, TableName, comment, curdField, contrNode);
            setCurdRequestNodeByFlag(R, TableName, comment, curdField, contrNode);
            setCurdRequestNodeByFlag(D, TableName, comment, curdField, contrNode);
            setCurdRequestNodeByFlag(L, TableName, comment, curdField, contrNode);

            curdControllerNodes.add(contrNode);
        }

        return curdControllerNodes;
    }

    private void setCurdRequestNodeByFlag(String curdFlag, String TableName, String comment, Map<String, List<ProTableColumn>> curdField, ControllerNode contrNode) {

        if (curdFlag.indexOf(curdFlag) < 0) {
            return;
        }
        ResponseNode responseNode = new ResponseNode();
        List<ParamNode> paramNodes = new ArrayList<>();

        String url = String.format(BASE_HOST, TableName, L.equalsIgnoreCase(curdFlag) ? "page" : "info");// info(GET,POST,PUT,DELETE)/page(GET)
        String mm = C.equalsIgnoreCase(curdFlag) ? "POST" : U.equalsIgnoreCase(curdFlag) ? "PUT" : R.equalsIgnoreCase(curdFlag) ? "GET" : D.equalsIgnoreCase(curdFlag) ? "DELETE" : "GET";
        List<String> httpMethod = Arrays.asList(mm);

        // resJSON
        JSONObject resJson = new JSONObject();
        resJson.put("code", "/* 业务状态码 */");
        resJson.put("msg", "/* 业务状态信息 */");

        // params list
        if (D.equalsIgnoreCase(curdFlag) || R.equalsIgnoreCase(curdFlag)) {
            ParamNode paramNode = new ParamNode();
            paramNode.setName("id");
            paramNode.setRequired(true);
            paramNode.setType("Integer");
            paramNodes.add(paramNode);
        }
        if (L.equalsIgnoreCase(curdFlag)) {
            ParamNode paramNode = new ParamNode();
            paramNode.setName("current");
            paramNode.setRequired(true);
            paramNode.setType("Integer");
            paramNodes.add(paramNode);

            paramNode = new ParamNode();
            paramNode.setName("pageSize");
            paramNode.setRequired(true);
            paramNode.setType("Integer");
            paramNodes.add(paramNode);

            List<ProTableColumn> qList = curdField.get(Q);
            if (CollectionUtils.isNotEmpty(qList)) {
                for (int k = 0; k < qList.size(); k++) {
                    ProTableColumn unitC = qList.get(k);
                    paramNode = new ParamNode();

                    String cName = CommUtils.underlineToCamel(unitC.getName(), Boolean.FALSE);
                    paramNode.setName(cName);
                    paramNode.setRequired(true);
                    paramNode.setType(getJavaTypeFromJdbcType(unitC.getMysqlType()));
                    paramNodes.add(paramNode);
                }
            }

            JSONObject pageWrapper = new JSONObject();
            pageWrapper.put("total", " long 总条数");
            pageWrapper.put("current", "int 当前页");
            pageWrapper.put("pageSize", "int 每页显示条数");

            JSONArray rows = new JSONArray();
            List<ProTableColumn> lList = curdField.get(L);
            if (CollectionUtils.isNotEmpty(lList)) {
                JSONObject pJson = setResJson(lList);
                rows.add(pJson);
            }
            pageWrapper.put("rows", rows);
            resJson.put("data", pageWrapper);
        }

        // params
        if (C.equalsIgnoreCase(curdFlag) || U.equalsIgnoreCase(curdFlag)) {
            List<ProTableColumn> ccList = curdField.get(curdFlag);
            if (CollectionUtils.isNotEmpty(ccList)) {
                for (int k = 0; k < ccList.size(); k++) {
                    ProTableColumn unitC = ccList.get(k);
                    ParamNode paramNode = new ParamNode();

                    String cName = CommUtils.underlineToCamel(unitC.getName(), Boolean.FALSE);
                    paramNode.setName(cName);
                    paramNode.setRequired(true);
                    paramNode.setType(getJavaTypeFromJdbcType(unitC.getMysqlType()));
                    paramNodes.add(paramNode);
                }
            }
        }

        if (R.equalsIgnoreCase(curdFlag)) {
            List<ProTableColumn> rList = curdField.get(curdFlag);
            if (CollectionUtils.isNotEmpty(rList)) {
                JSONObject pJson = setResJson(rList);
                resJson.put("data", pJson);
            }
        }

        /**
         * curd apidoc 方法封装
         */
        List<RequestNode> requestNodes = new ArrayList<>();
        if (contrNode.getRequestNodes() == null) {
            contrNode.setRequestNodes(requestNodes);
        }

        RequestNode requestNode = new RequestNode();

        String methodName = String.format("%s-%s", TableName, curdFlag);
        // description deprecated methodName url method changeFlag:1
        requestNode.setUrl(url);
        requestNode.setChangeFlag(Byte.valueOf("3"));
        requestNode.setDeprecated(false);
        requestNode.setMethodName(methodName);
        requestNode.setMethod(httpMethod);
        requestNode.setDescription(String.format("%s【%s】信息", comment, CURD_TITLE.get(curdFlag)));
        requestNode.setCodeFileUrl(contrNode.getDocFileName() + ".html#" + methodName);

        /**
         * ### paramNodes-paramNode ###
         * name
         * type
         * required true
         */
        requestNode.setParamNodes(paramNodes);

        /**
         * ### responseNode ###
         * responseNode.toJsonApi()
         */
        responseNode.setStringResult(resJson.toJSONString());
        requestNode.setResponseNode(responseNode);

        contrNode.getRequestNodes().add(requestNode);

    }

    private JSONObject setResJson(List<ProTableColumn> rList) {
        JSONObject pJson = new JSONObject();
        for (int k = 0; k < rList.size(); k++) {
            ProTableColumn unitC = rList.get(k);

            String cName = CommUtils.underlineToCamel(unitC.getName(), Boolean.FALSE);
            String cType = getJavaTypeFromJdbcType(unitC.getMysqlType());
            String comm = unitC.getComment();
            pJson.put(cName, cType + " " + comm);
        }
        return pJson;
    }

    private JSONObject setBaseVoJson(List<ProFunArg> proFunArgs) {
        JSONObject pJson = new JSONObject();
        for (int k = 0; k < proFunArgs.size(); k++) {
            ProFunArg unitC = proFunArgs.get(k);

            String cName = unitC.getName();
            String cType = unitC.getArgTypeName().toLowerCase();
            String comm = Optional.ofNullable(unitC.getComment()).filter(Objects::nonNull).orElse("");
            String collectionType = unitC.getCollectionType();

            String formatJsonByCType = getFormatJsonByCType(Boolean.TRUE, collectionType, cType,comm);
            pJson.put(cName,  getObjectType(formatJsonByCType));

        }
        return pJson;
    }


    /**
     * 格式化全部对象参数
     * @param proFunArgs
     * @return
     */
    JSONObject getParamsDesc(List<ProFunArg> proFunArgs){
        // List<ProFunArg> proFunArgs = proFunArgMapper.fetchFunArgsByFunId(funId);
         JSONObject jsonParam = setBaseVoJson(proFunArgs);

        for (ProFunArg proFunArg : proFunArgs) {
            //存在对象型参数
            boolean paramPresent = Optional.ofNullable(proFunArg)
                    .filter(item->!Objects.isNull(item.getArgTypeId()))
                    .filter(item->"vo".equals(item.getArgType()))
                    .isPresent();
            if(paramPresent){
                String collectionType = proFunArg.getCollectionType();
                String name = proFunArg.getName();
                JSONObject jsonFormat = VoJsonFormat(proFunArg.getArgTypeId());


                String formatJsonByCType = getFormatJsonByCType(Boolean.FALSE, collectionType, jsonFormat);
                jsonParam.put(name,  getObjectType(formatJsonByCType));

            }
        }
        return jsonParam;
    }


    /**
     * 格式化json字符串
     * @param jsonString
     * @return
     */
    private String getPrettyString(String jsonString){
        JSONObject object = null;
        try{
            object  =JSON.parseObject(jsonString);
        }catch (Exception e){
            System.out.println(jsonString);
            System.err.println(e);
        }
        String pretty = JSON.toJSONString(object, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteDateUseDateFormat);
        return pretty;
    }



    /**
     * 获取返回值参数详情
     * @param jsonStr
     * @return
     */



    private  String getReturnVos( String jsonStr)
    {
        String jsonString = null;
        JSONObject jsonObject = JSON.parseObject(jsonStr);

        if(jsonObject == null) return "";

        Integer id = jsonObject.getInteger("id");
        //标识当前对象类型
        String dataType = jsonObject.getString("dataType");
        //标识当前对象名称
        String name = jsonObject.getString("name");
        //标识当前对象集合类型
        String collectionType = Optional.ofNullable(jsonObject.getString("collectionType")).orElse("");
        //标识泛型的下级元素
        String next = jsonObject.getString("next");
        //标识当前对象是否为泛型
        String paradigm = jsonObject.getString("paradigm");

        List<ProField> proFields = proFieldMapper.selectByFileId(id);


        if(Optional.ofNullable(id).isPresent()){
            JSONObject  jsonFormat = VoJsonFormat(id.longValue());

            if(Optional.ofNullable(next).isPresent()
                    && Optional.ofNullable(paradigm).filter(item->"yes".equals(item)).isPresent()) {
                Map<String, ProField> collect = proFields.stream()
                        .filter(Objects::nonNull)
                        .filter(item -> "T".equals(item.getDataType()))
                        .collect(Collectors.toMap(ProField::getName, Function.identity()));

                for (Map.Entry<String, ProField> stringProFieldEntry : collect.entrySet()) {
                    String key = stringProFieldEntry.getKey();
                    String returnVos = getReturnVos(next);
                    jsonFormat.put(key,getObjectType(returnVos));
                }
            }
            jsonString = jsonFormat.toJSONString();

          return   getFormatJsonByCType(Boolean.FALSE, collectionType, jsonString);
        }else {
            //以 String 形式返回数据
            jsonString = name;

           return getFormatJsonByCType(Boolean.TRUE, collectionType, jsonString);

        }

    }

    /**
     * 格式化输出对象类型
     * @param voId
     * @return
     */
    JSONObject  VoJsonFormat (Long voId)
    {
        JSONObject jsonFormat = new JSONObject();
        if(Objects.isNull(voId))
        {
            jsonFormat.put("err","无法解析对象信息");
        }
        List<ProTableColumn> proTableColumns = new ArrayList<>();
        ProFile proFile = proFileMapper.selectByPrimaryKey(voId);
        boolean superPresent = Optional.ofNullable(proFile).filter(item -> !Objects.isNull(item.getSuperId())).isPresent();
        if(superPresent){
            proTableColumns = proTableColumnMapper.selectByTableId(proFile.getSuperId());
        }
        List<ProField> proFields = proFieldMapper.selectByFileId(voId.intValue());

        for (int k = 0; k < proTableColumns.size(); k++) {
            ProTableColumn unitC = proTableColumns.get(k);
            String cName = CommUtils.underlineToCamel(unitC.getName(), Boolean.FALSE);
            String cType = getJavaTypeFromJdbcType(unitC.getMysqlType());
            String comm =  Optional.ofNullable(unitC.getComment()).filter(Objects::nonNull).orElse("");
            jsonFormat.put(cName, cType + " " + comm);
        }
        for (ProField proField : proFields) {
            String collectionType = Optional.ofNullable(proField.getCollectionType()).orElse("");

            if(Optional.ofNullable(proField)
                    .filter(item -> "base".equals(item.getDataType()))
                    .isPresent()) {
                String comment = Optional.ofNullable(proField.getComment()).orElse("").replaceAll("\"","\'");
                String fname = proField.getName();
                String ftypePath = proField.getTypePath().toLowerCase();

                String formatJsonByCType = getFormatJsonByCType(Boolean.TRUE, collectionType, ftypePath,comment);
                jsonFormat.put(fname,formatJsonByCType );
            }

            if(Optional.ofNullable(proField)
                    .filter(item -> "vo".equals(item.getDataType()))
                    .filter(item->Objects.nonNull(item.getTypeKey()))
                    .isPresent()){
                String fname = proField.getName();
                Long ftypeKey = Long.valueOf(proField.getTypeKey());

                String formatJsonByCType = getFormatJsonByCType(Boolean.FALSE, collectionType, VoJsonFormat(ftypeKey));
                jsonFormat.put(fname,formatJsonByCType);

            }
        }


        return jsonFormat;
    }

    /**
     * 根据json获取类型
     * @param returnVos
     * @return
     */
    Object getObjectType(String returnVos){
        if(returnVos.startsWith("{") && returnVos.endsWith("}")){
            return  JSONObject.parseObject(returnVos);
        }else if(returnVos.startsWith("[") && returnVos.endsWith("]")){
            return JSONArray.parseArray(returnVos);
        }else{
            return returnVos;
        }
    }

    /**
     * 格式化出对象或String类型
     * @param returnIsString
     * @param collectionType
     * @param formatJson
     * @param comment
     * @param <T>
     * @return
     */
   <T> String getFormatJsonByCType(boolean returnIsString ,String collectionType, T formatJson ,String... comment ){

       if(!returnIsString){
           switch (collectionType){
               case "list":
               case "set":
                   return String.format("[%s]",formatJson);
               case "map":
                   return String.format("{\"string\":%s}",formatJson);
               case "single":
               case "":
                   return String.format("%s",formatJson);
               default:break;
           }
       }else{
           String comm = "";
           if(comment.length>0){
               comm = comment[0];
           }
           switch (collectionType){
               case "list":
                   return String.format("[%s] %s ",formatJson,comm);
               case "set":
                   return String.format("[%s]  %s ",formatJson,comm);
               case "map":
                   return String.format("{ string : %s } %s ",formatJson,comm);
               case "single":
               case "":
                   return String.format("%s  %s ",formatJson,comm);
               default:break;
           }
       }

        return String.valueOf(formatJson);
    }



}
